--- id: TASK-016 title: League standings view status: "\U0001F3C1 Done" assignee: - '@humdrum-tiv' created_date: '2026-06-17 17:43' updated_date: '2026-06-18 01:17' labels: - feature dependencies: [] priority: medium ordinal: 16000 --- ## Description Show league tables/standings (W-L, GP, pts, GB/GD, rank) for the selected league, fetched from ESPN's standings endpoint. A new view reachable from the dashboard; respects the user's enabled-league set. Soccer = group/table standings; stick-and-ball = division/conference records. ## Acceptance Criteria - [x] #1 User can open a standings view for a single league - [x] #2 Standings show rank, record, and league-appropriate columns (pts/GB/GD) - [x] #3 View uses the ESPN standings endpoint and the existing model/espn/ui layering - [x] #4 Only enabled leagues (App.leagues) are selectable ## Implementation Plan 1. model.Standings{League,Groups[]} -> StandingsGroup{Name,Columns[],Rows[]} -> StandingsRow{Rank,Team,Values[]}. 2. espn.Client.Standings(ctx,l): GET apis/v2 base (not apis/site/v2); recursive walk of children -> emit a group per node that has entries (soccer=groups, MLB=AL/NL, NBA/NHL/NFL=conferences). Per-sport column spec (soccer P/W/D/L/GD/Pts; baseball+nba PCT/GB; nfl T; nhl OTL/PTS). Stats keyed by type->displayValue. Rank = entry index. 3. UI viewStandings: open with key from dashboard for selected league (filter=All -> first enabled; tab/arrows cycle leagues). Own cursor over flattened team rows; enter opens that team's schedule (TASK-017). esc closes. Grouped, ranked, team-colored tables; scroll-clamped body like detail. ## Final Summary Added a standings view (viewStandings, opened with 'S'). What: - model.Standings (Groups -> StandingsGroup{Name,Columns,Rows}) with RowCount/RowAt flattening helpers for cursor navigation. - espn.Client.Standings fetches from ESPN's standings host (apis/v2/sports, NOT the scoreboard's apis/site/v2). mapStandings recursively walks ESPN's nested league/conference tree, emitting one group per table that has entries (soccer=12 groups, MLB=AL/NL, NBA/NHL/NFL=conferences). Per-sport column spec (soccer P/W/D/L/GD/Pts; baseball/nba W/L/PCT/GB; nfl adds T; nhl OTL/PTS); stats pulled by ESPN type. Rank = entry order. - UI: standings.go renders grouped, ranked, team-colored tables with a movable team-row cursor and scroll-follow. Open from the dashboard for the active league (all-leagues -> first enabled); tab/shift+tab cycle leagues, r refreshes, enter opens the selected team's schedule (TASK-017), esc closes. Only enabled leagues (App.leagues) are selectable. Tests: mapStandings (nested flatten + per-sport columns + missing-stat blanks), Standings.RowAt/RowCount. Live-smoke verified MLB AL/NL and World Cup 12 groups map correctly. go vet + go test ./... pass.